package com.franklin.ideaplugin.easytesting.editor;

import cn.hutool.core.util.StrUtil;
import com.franklin.ideaplugin.api.beans.GenericClass;
import com.franklin.ideaplugin.api.beans.GenericParameter;
import com.franklin.ideaplugin.api.utils.PsiUtil;
import com.franklin.ideaplugin.easytesting.common.utils.MethodUtils;
import com.franklin.ideaplugin.easytesting.compiler.constant.CompilerConstants;
import com.google.common.collect.Sets;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiType;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 转换器代码构造器
 *
 * @author Ye Junhui
 * @since 2023/7/31
 */
public class ExecuteMethodTransformerBuilder {


    private final static String CLASS_DOC_TEMPLATE =
            "/**\n" +
                    " * %s 参数转换器<br>\n" +
                    " * 使用方法:<br>\n" +
                    " * 1.类中包含无参构造器(构造器不填或显示声明无参构造器)<br>\n" +
                    " * 2.支持Spring @Autowired/@Resource 参数注入,@Qualifier指定bean名称注入<br>\n" +
                    " * 3.对应参数转换方法名必须与参数名保持一致<br>\n" +
                    " * 4.包名与类名不能修改<br>\n" +
                    " */";

    private final static String CLASS_TEMPLATE = ""
            + "public class %s {\n"
            + "\n%s\n"
            + "}";

    public static String buildScript(PsiMethod psiMethod) {
        //包名
        String packageCode = buildPackageCode();

        //导包
        List<String> importList = buildImportList(psiMethod);
        importList.add(Map.class.getName());
        String importCode = importList.stream()
                .map(className -> String.format("import %s;", className))
                .collect(Collectors.joining("\n"));

        //方法
        List<String> methodCodeList = buildMethodCodeList(psiMethod);
        methodCodeList.add(0,buildHeaderMethodCode());
        String methodCode = methodCodeList.stream()
                .collect(Collectors.joining("\n"));

        //类
        String classCode = buildClassCode(psiMethod, methodCode);

        return Stream.of(packageCode, importCode, classCode)
                .collect(Collectors.joining("\n\n"));
    }

    private static String buildPackageCode() {
        return "package " + CompilerConstants.PACKAGE_NAME + ";";
    }

    /**
     * 构建类名
     * @param psiMethod
     * @return
     */
    public static String buildClassName(PsiMethod psiMethod){
        PsiClass psiClass = psiMethod.getContainingClass();
        String className = StrUtil.upperFirst(psiMethod.getName());
        if (Objects.nonNull(psiClass)){
            String qualifiedName = psiClass.getQualifiedName();
            return MethodUtils.getScriptClassName(qualifiedName,psiMethod.getName());
        }
        return className;
    }

    /**
     * 构建类代码
     * @param psiMethod
     * @param methodCode
     * @return
     */
    private static String buildClassCode(PsiMethod psiMethod, String methodCode) {
        //doc
        String doc = String.format(CLASS_DOC_TEMPLATE, psiMethod.getName());

        //className
        String className = buildClassName(psiMethod);

        //code
        String classCode = String.format(CLASS_TEMPLATE, className, methodCode);
        return doc + "\n" + classCode;
    }

    /**
     * 方法代码
     *
     * @param psiMethod
     * @return
     */
    private static List<String> buildMethodCodeList(PsiMethod psiMethod) {
        List<PsiParameter> psiParameterList = PsiUtil.getParametersOfPsiMethod(psiMethod);
        return psiParameterList.stream()
                .map(ExecuteMethodTransformerBuilder::buildMethodCode)
                .collect(Collectors.toList());
    }

    /**
     * 构建请求头方法代码
     * @return
     */
    public static String buildHeaderMethodCode() {
        //注释
        String docCode =
                "\t/**\n" +
                "\t * 通过代码生成 请求头 的值\n" +
                "\t * @param headerMap 表单传入值\n" +
                "\t */\n";

        //方法代码
        String methodCode = buildMethodCode("Map<String, String>",CompilerConstants.HEADER_METHOD_NAME,"headerMap", false);
        return docCode + methodCode;
    }

    /**
     * 构建参数方法代码
     *
     * @param psiParameter
     * @return
     */
    private static String buildMethodCode(PsiParameter psiParameter) {
        PsiType psiType = psiParameter.getType();
        String typeText = psiType.getPresentableText();
        String methodName = psiParameter.getName();
        String paramName = methodName;
        String docCode =
                "\t/**\n" +
                "\t * 通过代码生成 " + paramName + " 的值\n" +
                "\t * @param " + paramName + " 表单传入值\n" +
                "\t * @return " + paramName + "'s true value\n" +
                "\t */\n";
        String code = buildMethodCode(typeText,methodName,paramName, true);
        return docCode + code;
    }

    /**
     * 构建方法代码
     * @param typeText
     * @param methodName
     * @param paramName
     * @param hasReturnType
     * @return
     */
    private static String buildMethodCode(String typeText,String methodName,String paramName,boolean hasReturnType){
        String returnType = typeText;
        String returnStatement = "\t\treturn " + paramName + ";\n";
        if (!hasReturnType){
            returnType = "void";
            returnStatement = "\t\t\n";
        }
        return ""
                + "\tpublic " + returnType + " " + methodName + "(" + typeText + " " + paramName + "){\n"
                + returnStatement
                + "\t}\n";
    }

    /**
     * 导包
     *
     * @param psiMethod
     * @return
     */
    private static List<String> buildImportList(PsiMethod psiMethod) {
        //入参
        Set<String> paramImports = buildImportParamSet(psiMethod);

        return Stream.of(paramImports)
                .flatMap(Collection::stream)
                .filter(type -> !PsiUtil.isBasicDataTypes(type))
                .distinct()
                .collect(Collectors.toList());
    }

    private static Set<String> buildImportReturnSet(PsiMethod psiMethod) {
        Set<String> resultSet = Sets.newLinkedHashSet();
        Project project = psiMethod.getProject();
        PsiType returnType = psiMethod.getReturnType();
        GenericClass genericClass = PsiUtil.resolveGenericClass(project, returnType);
        if (Objects.nonNull(genericClass)) {
            buildImportGenericSet(genericClass, resultSet);
        } else {
            resultSet.add(returnType.getCanonicalText());
        }
        return resultSet;
    }

    private static Set<String> buildImportParamSet(PsiMethod psiMethod) {
        List<PsiParameter> psiParameterList = PsiUtil.getParametersOfPsiMethod(psiMethod);
        Set<PsiType> cache = Sets.newHashSet();
        Set<String> resultSet = Sets.newLinkedHashSet();
        Project project = psiMethod.getProject();
        for (PsiParameter psiParameter : psiParameterList) {
            PsiType psiParameterType = psiParameter.getType();
            if (cache.contains(psiParameterType)) {
                continue;
            }
            cache.add(psiParameterType);
            GenericClass genericClass = PsiUtil.resolveGenericClass(project, psiParameterType);
            if (Objects.nonNull(genericClass)) {
                buildImportGenericSet(genericClass, resultSet);
            } else {
                resultSet.add(psiParameterType.getCanonicalText());
            }
        }
        return resultSet;
    }

    /**
     * 泛型类型
     *
     * @param genericClass
     * @param resultSet
     */
    private static void buildImportGenericSet(GenericClass genericClass, Set<String> resultSet) {
        if (Objects.isNull(genericClass)) {
            return;
        }
        PsiClass psiClass = genericClass.getPsiClass();
        resultSet.add(psiClass.getQualifiedName());

        //递归
        for (GenericParameter genericParameter : genericClass.getGenericParameterList()) {
            buildImportGenericSet(genericParameter.getGenericClass(), resultSet);
        }
    }
}
